#include <cassert>
#include <ctime>
#include <SFML/System/Sleep.hpp>
#include <SFML/Window/Event.hpp>
#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/Image.hpp>
#include <SFML/Graphics/CircleShape.hpp>
#include <nice2d/application.h>
#include <nice2d/message.h>
#include <nice2d/node.h>
#include <nice2d/scene.h>
#include <nice2d/proxymessagelistener.h>
#include <nice2d/components/fontmanager.h>
#include <nice2d/components/imagemanager.h>
#include <nice2d/components/fps.h>
#include <nice2d/components/defaultcamera.h>
#include <nice2d/components/soundmanager.h>
#include <nice2d/widgets/desktop.h>

IMPLEMENT_OBJECT(Application, Object)

class ApplicationData
{
public:
    std::shared_ptr<sf::RenderWindow> window;
    sf::Color backgroundColor;
    std::list<std::function<void()>> exitCallbacks;

    std::shared_ptr<Desktop> desktop;
};

std::shared_ptr<Application> Application::instance(new Application());

std::shared_ptr<Application> Application::getInstance()
{
    return instance;
}

Application::~Application()
{
}

void Application::setBackgroundColor(const sf::Color &color)
{
    data->backgroundColor = color;
}

void Application::installComponents()
{
    ADD_COMPONENT(this, Fps);
    ADD_COMPONENT(this, FontManager);
    ADD_COMPONENT(this, ImageManager);
    ADD_COMPONENT(this, DefaultCamera);
    ADD_COMPONENT(this, SoundManager);
    data->desktop = ADD_COMPONENT(this, Desktop);
}

bool Application::execute(std::shared_ptr<Node> object)
{
    assert(data->window);
    assert(object);

    auto fn = [&](std::shared_ptr<Message> message)->bool {
        return object->process(message);
    };

    auto listener = std::make_shared<ProxyMessageListener>(fn);
    registerMessageListener(listener);

    sf::Clock clock;
    while (data->window->isOpen()) {
        sf::Event event;
        while (data->window->pollEvent(event)) {
            if ((event.type == sf::Event::Closed) ||
                    ((event.type == sf::Event::KeyPressed) && (event.key.code == sf::Keyboard::Escape))) {
                exit();
                break;
            }

            auto message = std::make_shared<SFMLMessage>(event);

            bool done = false;
            if (data->desktop)
                done = data->desktop->process(message);

            if (!done)
                process(message);
        }

        data->window->clear(data->backgroundColor);
        data->window->setActive(true);

        float deltaTime = clock.restart().asSeconds();
        if (isRunning()) {
            object->update(deltaTime);
            updateComponent(deltaTime);
        }

        data->window->draw(*object);

        if (data->desktop)
            data->window->draw(*data->desktop);

        data->window->display();

        sf::sleep(sf::milliseconds(10));
    }

    auto scene = std::dynamic_pointer_cast<Scene>(object);
    if (scene)
        scene->exit();

    return true;
}

void Application::setWindow(std::shared_ptr<sf::RenderWindow> inputWindow)
{
    data->window = inputWindow;
}

std::shared_ptr<sf::RenderWindow> Application::getWindow() const
{
    return data->window;
}

sf::Vector2f Application::getMousePosition()
{
    auto window = getWindow();
    if (!window)
        return sf::Vector2f();
    return window->mapPixelToCoords(sf::Mouse::getPosition(*window));
}

void Application::addExitCallback(std::function<void ()> callback)
{
    data->exitCallbacks.push_back(callback);
}

void Application::exit()
{
    std::for_each(data->exitCallbacks.begin(), data->exitCallbacks.end(), [](std::function<void()> cb) {
        cb();
    });

    if (data->window)
        data->window->close();
}

Application::Application():
    data(new ApplicationData())
{
    srand(time(nullptr));
    setlocale(LC_ALL, "");
}
